package com.jreiffers.uihandlers;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.List;

public class UiHandler {

	private UiHandler() {
	}

	public static class UiHandlerException extends Exception {

		/**
		 * 
		 */
		private static final long serialVersionUID = -4694439331311307660L;

		/**
		 * 
		 */
		public UiHandlerException() {
		}

		/**
		 * @param message
		 * @param cause
		 */
		public UiHandlerException(String message, Throwable cause) {
			super(message, cause);
		}

		/**
		 * @param message
		 */
		public UiHandlerException(String message) {
			super(message);
		}

		/**
		 * @param cause
		 */
		public UiHandlerException(Throwable cause) {
			super(cause);
		}

	}

	/**
	 * Takes all methods annotated with {@link EventHandler} and creates the EventHandlers.
	 * 
	 * @param o
	 *            the object whose event handlers to set up
	 */
	public static void safeSetup(final Object o) throws UiHandlerException {
		nextMethod: for (final Method m : o.getClass().getMethods()) {
			if (m.isAnnotationPresent(EventHandler.class)) {
				Class<?>[] parameters = m.getParameterTypes();
				if (parameters.length != 1) {
					throw new IllegalArgumentException("Method " + m.getName()
							+ " should have exactly one parameter.");
				}

				EventHandler an = m.getAnnotation(EventHandler.class);
				try {
					String[] fields = an.value();

					for (String field : fields) {
						Field f = field.equals("") ? null : o.getClass().getDeclaredField(field);
						if (f != null)
							f.setAccessible(true);

						for (EventType type : EventType.values()) {
							if (parameters[0].equals(type.paramClass)) {
								if (an.hint() == type.hint) {
									processEvent(o, m, an, f, type, false);
									continue nextMethod;
								} else if (type == EventType.LISTENER_EVENT) {
									processEvent(o, m, an, f, type, true);
								}
							}
						}
					}
				} catch (SecurityException e1) {
					throw new UiHandlerException(e1);
				} catch (NoSuchFieldException e1) {
					throw new UiHandlerException("Field " + an.value() + " of class "
							+ o.getClass() + " does not exist.", e1);
				}
			}
		}
	}

	@SuppressWarnings({ "rawtypes" })
	private static void processEvent(final Object o, final Method m, EventHandler an, Field f,
			EventType type, boolean swtEvent) throws UiHandlerException {
		Object fieldValue;
		try {
			Object adapted = type.adapter.adapt(o, m);
			fieldValue = f == null ? o : f.get(o);
			if (an.list()) {
				if (!(fieldValue instanceof List)) {
					throw new UiHandlerException(
							"Field "
									+ an.value()
									+ " of "
									+ o.getClass()
									+ " is not a list, although list attribute was specified in EventHandler.");
				}
				List fieldList = (List) fieldValue;
				for (Object fieldElement : fieldList) {
					addListener(o, an, type, fieldElement, adapted, swtEvent);
				}
			} else {
				addListener(o, an, type, fieldValue, adapted, swtEvent);
			}
		} catch (IllegalAccessException e1) {
			throw new UiHandlerException("Field " + an.value() + " of " + o.getClass()
					+ " is not accessible.", e1);
		}
	}

	private static void addListener(final Object o, EventHandler an, EventType type,
			Object fieldValue, Object adapted, boolean swtEvent) throws UiHandlerException {
		try {
			if (swtEvent) {
				Method addAListener = fieldValue.getClass().getMethod(type.methodName, int.class,
						type.methodParam);
				addAListener.invoke(fieldValue, an.event(), adapted);
			} else {
				Method addAListener = fieldValue.getClass().getMethod(type.methodName,
						type.methodParam);
				addAListener.invoke(fieldValue, adapted);
			}
		} catch (NoSuchMethodException e) {
			if (fieldValue instanceof List && !an.list()) {
				throw new UiHandlerException("Field " + an.value() + " of " + o.getClass()
						+ " contained a List, but the EventHandler did not "
						+ "specifiy the list attribute. Maybe you forgot it.", e);
			}
			throw new UiHandlerException(e);
		} catch (InvocationTargetException e) {
			throw new UiHandlerException(e);
		} catch (IllegalAccessException e) {
			throw new UiHandlerException(e);
		}
	}

	/**
	 * Like safeSetup, but will rethrow any exceptions as RuntimeExceptions.
	 * 
	 * @param p
	 *            the object whose EventHandlers to set up
	 */
	public static void setup(Object p) {
		try {
			safeSetup(p);
		} catch (UiHandlerException e) {
			throw new RuntimeException(e);
		}
	}
}
